﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.IO;
using System.Threading;
using System.Drawing.Imaging;

namespace BioCompEx2
{
    public partial class DataForm : Form
    {
        public static String encodedText;
        public static Hashtable wordDict;
        public static Hashtable keyedDict;
        public static int maxGrade;
        private static double bestGrade;
        public static uint currentCycle;
        public static uint sleepTimeInMilisecs;
        private Boolean set = false;

        //Average Graph
        private Bitmap averageDrawArea;
        private System.Drawing.Graphics averageGraphicsObj;
        //Best Graph
        private Bitmap bestDrawArea;
        private System.Drawing.Graphics bestGraphicsObj;
        //Worst Graph
        private Bitmap worstDrawArea;
        private System.Drawing.Graphics worstGraphicsObj;

        delegate void SetTextCallback(string text);

        delegate void SetFloatCallback(float value);
        private Thread cyclesThread = null;



        public DataForm()
        {
            InitializeComponent();
            currentCycle = 0;
            sleepTimeInMilisecs = 150;
        }

        private void splitContainer1_Panel1_Paint(object sender, PaintEventArgs e)
        {

        }

        private void splitContainer1_Panel2_Paint(object sender, PaintEventArgs e)
        {

        }

        //calculate the possible maximum grade (each word's length is squared and then summed)
        private static void calcMaxGrade()
        {
            maxGrade = 0;
            char[] separator = new char[1];
            separator[0]=' ';
            String[] splittedText = encodedText.Split(separator);
            foreach (String word in splittedText)
            {
                maxGrade += (int)Math.Pow(word.Length,2);
            }
        }

       


        //Genetic algorithm management
        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        private void cyclesManager()
        {
            AppendConsoleTextBox("Cycles manager thread started\r\n");
            //Create the solution set
            SolutionSet solutionSet = new SolutionSet((float)mutationNumericUpDown.Value,
                                                    (int)crossoverProbabilityNumericUpDown.Value,
                                                    (int)amountOfSolutionsNumericUpDown.Value);
            bestGrade = 0;
            for (uint i = 1; i <= maximumCyclesNumericUpDown.Value; i++)
            {
                AppendConsoleTextBox("Cycle [" + i.ToString() + "] started\r\n");
                currentCycle = i;

                Thread.Sleep((int)sleepTimeInMilisecs);
                SetCurrentCycleNumberLabel(i.ToString());

                solutionSet.performCycle();
                double bestGraphGrade = (100 * (double)solutionSet.TopGrade / maxGrade);
                if (i % 5 == 0)   //each 5 cycles
                {   //if we didn't improve
                    if (!(bestGraphGrade > (bestGrade + 0.1) || bestGraphGrade < (bestGrade - 0.1)))
                        solutionSet.PM = 0.6;   //set mutation to 60% for the next 5 cycles
                    else
                        solutionSet.resetPm();  //else return it to its original value for at least the next 5 cycles
                    bestGrade = bestGraphGrade;
                }
                //Update form graphs with normelized values (1-100) and the best scores values
                AppendConsoleTextBox("Current average grade [" + (100 * (double)solutionSet.AverageGrade / maxGrade).ToString()+ "]\r\n");
                SetAverageGradeLabel((100 * (double)solutionSet.AverageGrade / maxGrade).ToString());
                averageGraphUpdate(solutionSet.AverageGrade);

                AppendConsoleTextBox("Current best grade [" + (100 * (double)solutionSet.TopGrade / maxGrade).ToString() + "]\r\n");
                SetBestGradeLabel(bestGraphGrade.ToString());
                bestGraphUpdate((float)solutionSet.TopGrade);

                AppendConsoleTextBox("Current worst grade [" + (100 * (double)solutionSet.LowestGrade / maxGrade).ToString() + "]\r\n");
                SetWorstGradeLabel((100 * (double)solutionSet.LowestGrade / maxGrade).ToString());
                worstGraphUpdate((float)solutionSet.LowestGrade);

                SetDecodedStringTextBox(solutionSet.BestDecodedSTR);
                SetBestGradeEverLabel(Convert.ToString(solutionSet.BestGrade));
                if (solutionSet.TopGrade == maxGrade && !set)
                    SetFirstCycleToPerfect(i.ToString());
            }
        }

        //Controll setters for thread safe setting
        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        private void SetCurrentCycleNumberLabel(string text)
        {       
            // InvokeRequired required compares the thread ID of the
            // calling thread to the thread ID of the creating thread.
            // If these threads are different, it returns true.
            if (this.currentCycleNumberLabel.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetCurrentCycleNumberLabel);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.currentCycleNumberLabel.Text = text;
            }
        }

        private void SetAverageGradeLabel(string text)
        {
            if (this.averageGradeNumberLabel.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetAverageGradeLabel);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.averageGradeNumberLabel.Text = text;
            }
        }

        private void SetBestGradeLabel(string text)
        {
            if (this.bestGradeNumberLabel.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetBestGradeLabel);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.bestGradeNumberLabel.Text = text;
            }
        }

        private void SetBestGradeEverLabel(string text)
        {
            if (this.lblBestGrade.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetBestGradeEverLabel);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.lblBestGradeNum.Text = text;
            }
        }

        private void SetFirstCycleToPerfect(string text)
        {
            if (this.lblFirstCycleNum.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetFirstCycleToPerfect);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                set = true;
                this.lblFirstCycleNum.Text = text;
            }
        }

        private void SetWorstGradeLabel(string text)
        {
            if (this.worstGradeNumberLabel.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetWorstGradeLabel);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.worstGradeNumberLabel.Text = text;
            }
        }

        private void SetDecodedStringTextBox(string text)
        {
            if (this.decodedStringTextBox.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetDecodedStringTextBox);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.decodedStringTextBox.Text = text;
            }
        }

        private void AppendConsoleTextBox(string text)
        {
            if (this.decodedStringTextBox.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(AppendConsoleTextBox);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.consoleTextBox.Text += text;                                
            }
        }

        //Graph drawing methods
        //~~~~~~~~~~~~~~~~~~~~~~~~
        private void averageGraphUpdate(float value)
        {
            if (this.averageGradePictureBox.InvokeRequired)
            {
                SetFloatCallback d = new SetFloatCallback(averageGraphUpdate);
                this.Invoke(d, new object[] { value });
            }
            else
            {

                int lineWidth = 1;
                int xPos = (int)currentCycle % averageDrawArea.Width;
                float normalizedGrade = value / maxGrade;

                Pen myPen = new Pen(Color.White, lineWidth);

                //Draw background vertical line
                myPen.Color = Color.Black;
                averageGraphicsObj.DrawLine(myPen, (new Point(xPos, 0)), (new Point(xPos, averageGradePictureBox.Height)));

                myPen.Color = Color.Red;
                float pixelHeight = averageGradePictureBox.Height / 100;
                float pixelYPos = -2 + averageGradePictureBox.Height - (normalizedGrade * averageGradePictureBox.Height);

                averageGraphicsObj.DrawRectangle(myPen, new Rectangle(xPos,
                                                                (int)pixelYPos,
                                                                lineWidth,
                                                                1));//(int)pixelHeight));

                myPen.Color = Color.White;
                averageGraphicsObj.DrawRectangle(myPen, new Rectangle(xPos,
                                                                averageGradePictureBox.Height - 1,
                                                                lineWidth,
                                                                1));
                myPen.Color = Color.Black;
                averageGraphicsObj.DrawRectangle(myPen, new Rectangle(xPos-1,
                                                                averageGradePictureBox.Height - 1,
                                                                lineWidth,
                                                                1));

                myPen.Dispose();

                averageGradePictureBox.Invalidate();
                averageGradePictureBox.Refresh();
                averageGradePictureBox.Update();
            }
        }

        private void bestGraphUpdate(float value)
        {
            if (this.bestGradePictureBox.InvokeRequired)
            {
                SetFloatCallback d = new SetFloatCallback(bestGraphUpdate);
                this.Invoke(d, new object[] { value });
            }
            else
            {

                int lineWidth = 1;
                int xPos = (int)currentCycle % bestDrawArea.Width;
                float normalizedGrade = value / maxGrade;

                Pen myPen = new Pen(Color.White, lineWidth);

                //Draw background vertical line
                myPen.Color = Color.Black;
                bestGraphicsObj.DrawLine(myPen, (new Point(xPos, 0)), (new Point(xPos, bestGradePictureBox.Height)));

                myPen.Color = Color.Red;
                float pixelHeight = bestGradePictureBox.Height / 100;
                float pixelYPos = -2 + bestGradePictureBox.Height - (normalizedGrade * bestGradePictureBox.Height);

                bestGraphicsObj.DrawRectangle(myPen, new Rectangle(xPos,
                                                                (int)pixelYPos,
                                                                lineWidth,
                                                                1));//(int)pixelHeight));

                myPen.Color = Color.White;
                bestGraphicsObj.DrawRectangle(myPen, new Rectangle(xPos,
                                                                bestGradePictureBox.Height - 1,
                                                                lineWidth,
                                                                1));
                myPen.Color = Color.Black;
                bestGraphicsObj.DrawRectangle(myPen, new Rectangle(xPos - 1,
                                                                bestGradePictureBox.Height - 1,
                                                                lineWidth,
                                                                1));

                myPen.Dispose();

                bestGradePictureBox.Invalidate();
                bestGradePictureBox.Refresh();
                bestGradePictureBox.Update();
            }
        }

        private void worstGraphUpdate(float value)
        {
            if (this.worstGradePictureBox.InvokeRequired)
            {
                SetFloatCallback d = new SetFloatCallback(worstGraphUpdate);
                this.Invoke(d, new object[] { value });
            }
            else
            {

                int lineWidth = 1;
                int xPos = (int)currentCycle % worstDrawArea.Width;
                float normalizedGrade = value / maxGrade;

                Pen myPen = new Pen(Color.White, lineWidth);

                //Draw background vertical line
                myPen.Color = Color.Black;
                worstGraphicsObj.DrawLine(myPen, (new Point(xPos, 0)), (new Point(xPos, worstGradePictureBox.Height)));

                myPen.Color = Color.Red;
                float pixelHeight = worstGradePictureBox.Height / 100;
                float pixelYPos = -2 + worstGradePictureBox.Height - (normalizedGrade * worstGradePictureBox.Height);

                worstGraphicsObj.DrawRectangle(myPen, new Rectangle(xPos,
                                                                (int)pixelYPos,
                                                                lineWidth,
                                                                1));//(int)pixelHeight));

                myPen.Color = Color.White;
                worstGraphicsObj.DrawRectangle(myPen, new Rectangle(xPos,
                                                                worstGradePictureBox.Height - 1,
                                                                lineWidth,
                                                                1));
                myPen.Color = Color.Black;
                worstGraphicsObj.DrawRectangle(myPen, new Rectangle(xPos - 1,
                                                                worstGradePictureBox.Height - 1,
                                                                lineWidth,
                                                                1));

                myPen.Dispose();

                worstGradePictureBox.Invalidate();
                worstGradePictureBox.Refresh();
                worstGradePictureBox.Update();
            }
        }

        ////Form events handling
        //~~~~~~~~~~~~~~~~~~~~~~

        private void startButton_Click(object sender, EventArgs e)
        {
            startButton.Enabled = false;
            stopButton.Enabled = true;
            sleepTimeInMilisecs = (uint)cycleSleepNumericUpDown.Value;
            //Initialize graphical objects
            if (averageDrawArea == null)
            {
                averageDrawArea = new Bitmap(averageGradePictureBox.Width, averageGradePictureBox.Height, PixelFormat.Format16bppRgb555);
                averageGradePictureBox.Image = averageDrawArea;
                averageGraphicsObj = Graphics.FromImage(this.averageDrawArea);

                bestDrawArea = new Bitmap(bestGradePictureBox.Width, bestGradePictureBox.Height, PixelFormat.Format16bppRgb555);
                bestGradePictureBox.Image = bestDrawArea;
                bestGraphicsObj = Graphics.FromImage(this.bestDrawArea);

                worstDrawArea = new Bitmap(worstGradePictureBox.Width, worstGradePictureBox.Height, PixelFormat.Format16bppRgb555);
                worstGradePictureBox.Image = worstDrawArea;
                worstGraphicsObj = Graphics.FromImage(this.worstDrawArea);
            }
            //start the cycles thread
            cyclesThread = new Thread(new ThreadStart(cyclesManager));
            cyclesThread.Start();
        }

        //stop the cycles thread
        private void stopButton_Click(object sender, EventArgs e)
        {            
            if (cyclesThread != null)
            {
                cyclesThread.Abort();
                AppendConsoleTextBox("Aborting cyclesManager thread\r\n");
            }
            startButton.Enabled = true;
        }

        //open a browser window to select the dictionary file
        private void chooseDictionaryButton_Click(object sender, EventArgs e)
        {
            if (dictionaryOpenFileDialog.ShowDialog() == DialogResult.OK)
            {
                chooseDictionaryTextBox.Text = dictionaryOpenFileDialog.FileName;
            }
        }

        //open a browser window to select the encoded text file
        private void chooseEncodedButton_Click(object sender, EventArgs e)
        {
            if (codedFileOpenFileDialog.ShowDialog() == DialogResult.OK)
            {
                chooseEncodedTextBox.Text = codedFileOpenFileDialog.FileName;
            }
        }

        //initialize the ditionaries
        private void processButton_Click(object sender, EventArgs e)
        {
            AppendConsoleTextBox("Pre processing phase started\r\n");
            AppendConsoleTextBox("Opening encoded string from file - " + chooseEncodedTextBox.Text + "\r\n");
            StreamReader inCode = new StreamReader(chooseEncodedTextBox.Text);
            AppendConsoleTextBox("Opening dictionary from file - " + chooseDictionaryTextBox.Text + "\r\n");
            StreamReader inDict = new StreamReader(chooseDictionaryTextBox.Text);

            wordDict = new Hashtable();
            keyedDict = new Hashtable();
            encodedText = inCode.ReadToEnd();
            String word;
            AppendConsoleTextBox("Calculating words hash\r\n");
            do
            {
                word = inDict.ReadLine();
                if (word != null)
                {
                    word = word.Trim();
                    wordDict.Add(word, word);   //each word is inserted as is to the regular hash dictionary
                    //each word is inserted to another hash that connect a list of words to their length as a key
                    //the actual word keying was "dropped" after testing its usability
                    LinkedList<KeyedWord> keyedWordsList = (LinkedList<KeyedWord>)(keyedDict[word.Length]);
                    if (keyedWordsList == null)
                        keyedWordsList = new LinkedList<KeyedWord>();
                    keyedWordsList.AddLast(new KeyedWord(word));
                    keyedDict.Remove(word.Length);
                    keyedDict.Add(word.Length, keyedWordsList);
                }
            } while (word != null);   //EOF

            calcMaxGrade();

            inCode.Close();
            inDict.Close();

            startButton.Enabled = true;
            processButton.Enabled = false;
            maximumGradeNumberLabel.Text = maxGrade.ToString();
        }    
    }    
}